#include "LvPipeline.h"
#include "LvModel.h"

#include<cassert>
#include<fstream>
#include<iostream>


LvPipeline::LvPipeline(LvDevice& device,
	const std::string& vertPath,
	const std::string& fragPath,
	const PipelineConfigInfo& configInfo)
	: lvDevice(device)
{
	createGraphicsPipeline(vertPath, fragPath, configInfo);
}

LvPipeline::~LvPipeline()
{
	if (vertShaderModule != VK_NULL_HANDLE)
	{
		vkDestroyShaderModule(lvDevice.device(), vertShaderModule, nullptr);
	}
	if (fragShaderModule != VK_NULL_HANDLE)
	{
		vkDestroyShaderModule(lvDevice.device(), fragShaderModule, nullptr);
	}
	vkDestroyPipeline(lvDevice.device(), graphicsPipeline, nullptr);
}


void LvPipeline::bind(VkCommandBuffer commandBuffer)
{
	vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
}

void LvPipeline::defaultPipelineConfigInfo(PipelineConfigInfo& info)
{

	info.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
	info.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
	info.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;

	info.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
	info.rasterizationInfo.depthClampEnable = VK_FALSE;
	info.rasterizationInfo.rasterizerDiscardEnable = VK_FALSE;
	info.rasterizationInfo.polygonMode = VK_POLYGON_MODE_FILL;
	info.rasterizationInfo.lineWidth = 1.0f;
	info.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
	info.rasterizationInfo.frontFace = VK_FRONT_FACE_CLOCKWISE;
	info.rasterizationInfo.depthBiasEnable = VK_FALSE;
	info.rasterizationInfo.depthBiasConstantFactor = 0.0f;
	info.rasterizationInfo.depthBiasClamp = 0.0f;
	info.rasterizationInfo.depthBiasSlopeFactor = 0.0f;

	info.multisampleInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
	info.multisampleInfo.sampleShadingEnable = VK_FALSE;
	info.multisampleInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
	info.multisampleInfo.minSampleShading = 1.0f;
	info.multisampleInfo.pSampleMask = nullptr;
	info.multisampleInfo.alphaToCoverageEnable = VK_FALSE;
	info.multisampleInfo.alphaToOneEnable = VK_FALSE;

	info.colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
		VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
	info.colorBlendAttachment.blendEnable = VK_FALSE;
	info.colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
	info.colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
	info.colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
	info.colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
	info.colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
	info.colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;

	info.depthStencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
	info.depthStencilInfo.depthTestEnable = VK_TRUE;
	info.depthStencilInfo.depthWriteEnable = VK_TRUE;
	info.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
	info.depthStencilInfo.depthBoundsTestEnable = VK_FALSE;
	info.depthStencilInfo.minDepthBounds = 0.0f;
	info.depthStencilInfo.maxDepthBounds = 1.0f;
	info.depthStencilInfo.stencilTestEnable = VK_FALSE;
	info.depthStencilInfo.front = {};
	info.depthStencilInfo.back = {};


	info.dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
}

std::vector<char> LvPipeline::readFile(const std::string& filepath)
{
	std::ifstream file(filepath, std::ios::ate | std::ios::binary);

	if (!file.is_open())
	{
		throw std::runtime_error("failed to open file: " + filepath);
	}

	size_t fileSize = static_cast<size_t>(file.tellg());
	std::vector<char> buffer(fileSize);


	file.seekg(0);
	file.read(buffer.data(), fileSize);
	file.close();

	return buffer;
}

void LvPipeline::createGraphicsPipeline(
	const std::string& vertPath,
	const std::string& fragPath,
	const PipelineConfigInfo& configInfo)
{
	assert(
		configInfo.pipelineLayout != nullptr &&
		"Cannot create graphics pipeline: no pipelineLayout provided in config info");
	assert(
		configInfo.renderPass != nullptr &&
		"Cannot create graphics pipeline: no renderPass provided in config info");

	auto vertCode = readFile(vertPath);
	auto fragCode = readFile(fragPath);

	createShaderModule(vertCode, &vertShaderModule);
	createShaderModule(fragCode, &fragShaderModule);


	VkPipelineShaderStageCreateInfo shaderStages[2]{};
	shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
	shaderStages[0].module = vertShaderModule;
	shaderStages[0].pName = "main";
	shaderStages[0].flags = 0;
	shaderStages[0].pNext = nullptr;
	shaderStages[0].pSpecializationInfo = nullptr;
	shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
	shaderStages[1].module = fragShaderModule;
	shaderStages[1].pName = "main";
	shaderStages[1].flags = 0;
	shaderStages[1].pNext = nullptr;
	shaderStages[1].pSpecializationInfo = nullptr;

	auto bindingDescriptions = LvModel::Vertex::getBindingDescriptions();
	auto attributeDescriptions = LvModel::Vertex::getAttributeDescriptions();
	VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
	vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
	vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescriptions.size());
	vertexInputInfo.pVertexBindingDescriptions = bindingDescriptions.data();
	vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
	vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

	VkPipelineViewportStateCreateInfo viewportInfo{};
	viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
	viewportInfo.viewportCount = 1;
	viewportInfo.pViewports = nullptr;
	viewportInfo.scissorCount = 1;
	viewportInfo.pScissors = nullptr;

	VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
	colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
	colorBlendInfo.logicOpEnable = VK_FALSE;
	colorBlendInfo.logicOp = VK_LOGIC_OP_COPY;
	colorBlendInfo.attachmentCount = 1;
	colorBlendInfo.pAttachments = &configInfo.colorBlendAttachment;
	colorBlendInfo.blendConstants[0] = 0.0f;
	colorBlendInfo.blendConstants[1] = 0.0f;
	colorBlendInfo.blendConstants[2] = 0.0f;
	colorBlendInfo.blendConstants[3] = 0.0f;

	VkPipelineDynamicStateCreateInfo dynamicStateInfo{};
	dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
	dynamicStateInfo.pDynamicStates = configInfo.dynamicStateEnables.data();
	dynamicStateInfo.dynamicStateCount =static_cast<uint32_t>(configInfo.dynamicStateEnables.size());
	dynamicStateInfo.flags = 0;


	VkGraphicsPipelineCreateInfo pipelineInfo{};
	pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
	pipelineInfo.stageCount = 2;
	pipelineInfo.pStages = shaderStages;
	pipelineInfo.pVertexInputState = &vertexInputInfo;
	pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo;
	pipelineInfo.pViewportState = &viewportInfo;
	pipelineInfo.pRasterizationState = &configInfo.rasterizationInfo;
	pipelineInfo.pMultisampleState = &configInfo.multisampleInfo;
	pipelineInfo.pColorBlendState = &colorBlendInfo;
	pipelineInfo.pDynamicState = &dynamicStateInfo;
	pipelineInfo.pDepthStencilState = &configInfo.depthStencilInfo;

	pipelineInfo.layout = configInfo.pipelineLayout;
	pipelineInfo.renderPass = configInfo.renderPass;
	pipelineInfo.subpass = configInfo.subpass;

	pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
	pipelineInfo.basePipelineIndex = -1;

	if (vkCreateGraphicsPipelines(
		lvDevice.device(),
		VK_NULL_HANDLE,
		1, 
		&pipelineInfo,
		nullptr,
		&graphicsPipeline) != VK_SUCCESS)
	{
		throw std::runtime_error("failed to create graphics pipeline!");
	}

	vkDestroyShaderModule(lvDevice.device(), fragShaderModule, nullptr);
	vkDestroyShaderModule(lvDevice.device(), vertShaderModule, nullptr);
	fragShaderModule = VK_NULL_HANDLE;
	vertShaderModule = VK_NULL_HANDLE;
}

void LvPipeline::createShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule)
{
	VkShaderModuleCreateInfo createInfo{};
	createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
	createInfo.codeSize = code.size();
	createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());

	if (vkCreateShaderModule(lvDevice.device(), &createInfo, nullptr, shaderModule) != VK_SUCCESS)
	{
		throw std::runtime_error("failed to create shader module.");
	}
}
